import cv2
import numpy as np


# 银行卡实战小项目

def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


def sort(cnts, method="left-to-right"):
    reverse = False
    i = 0
    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True
    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]  # 用一个最小的矩形，把找到的形状包起来，然后对最小坐标进行排序
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))
    return cnts, boundingBoxes


# 1.读取模板
img = cv2.imread('ocr_a_reference.png')
cv_show('number', img)
# 2.模板转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('gray', gray)
# 3.换为二值图像
ref, thre = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY_INV)
cv_show('ref', thre)
# 4.计算轮廓
refCnts, hierarchy = cv2.findContours(thre.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, refCnts, -1, (0, 0, 255), 3)  # 画轮廓,此处只画外轮廓，一共10个轮廓0~9
cv_show('imgLK', img)
refCnts = sort(refCnts, "left-to-right")[0]
digits = {}  # 用来存放模板数字对应的数字

# 遍历每一个轮廓：
for (i, c) in enumerate(refCnts):  # 计算外接矩形并且resize成合适大小
    (x, y, w, h) = cv2.boundingRect(c)
    roi = thre[y:y + h, x:x + w]
    roi = cv2.resize(roi, (57, 88))

    # 每个数字都有一个模板
    digits[i] = roi  # 0~9的数字模板对应

# 初始化卷积核,做形态学处理,核的大小可以自己定义，根据实际情况进行定义
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

# 读取原始图像，预处理
image = cv2.imread('credit_card_01.png')
cv_show('card', image)
image = cv2.resize(image, (300, 200))
# 灰度处理
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv_show('gray', image_gray)
# 顶帽操作，突出明亮区域
tophat = cv2.morphologyEx(image_gray, cv2.MORPH_TOPHAT, rectKernel)
cv_show('tophat', tophat)
# 计算边界的梯度
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)

gradX = np.absolute(gradX)  # 取绝对值
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))  # 归一化
gradX = gradX.astype("uint8")
cv_show('gradX', gradX)

# 执行闭操作，让图像信息成块出现
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
cv_show('gradX', gradX)

thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]  # 阈值设为0是因为函数中设置了自动判断阈值，一般适用于双峰情况
cv_show('gradX_t', thresh)

# 再进行一次闭操作，让图像信息成团出现，补齐空白部分
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
conts, hiera = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cont = conts
curImag = image.copy()
tmp = cv2.drawContours(curImag, cont, -1, (0, 0, 255), 3)  # 在经过一系列处理后的图像中画出轮廓
locs = []

for (i, c) in enumerate(cont):
    (x, y, w, h) = cv2.boundingRect(c)  # 画出每个区域的外接矩形，后续根据外接矩形的长宽比进行筛选需要的部分
    ar = w / float(h)
    if ar > 2.5 and ar < 4.0:
        if (w > 40 and w < 55) and (h > 10 and h < 20):  # 选取出满足条件的框
            locs.append((x, y, w, h))
locs = sorted(locs, key=lambda x: x[0])  # 将筛选之后的轮廓数据进行排序
output = []
for (i, (gx, gy, gw, gh)) in enumerate(locs):
    groupOuput = []
    group = image_gray[gy - 5:gy + gh + 5, gx - 5:gx + gw + 5]  # 获取轮廓及其周围数据,加五减五的作用是将获取到的坐标位上下左右偏移一点，方便匹配
    cv_show('group', group)
    group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]  # 再次对每个大框里面的数据进行二值化、测边界
    digitsCont, hieraD = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 再进行轮廓检测
    digitsCont = sort(digitsCont, method="left-to-right")[0]
    for c in digitsCont:  # 计算每个小框的值
        (x, y, w, h) = cv2.boundingRect(c)  # 做同样操作，画外接矩形然后模式匹配
        roi = group[y:y + h, x:x + w]
        roi = cv2.resize(roi, (57, 88))
        scores = []
        for (digit, digitROI) in digits.items():
            result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF)  # 进行匹配,返回的最高值
            (_, score, _, _) = cv2.minMaxLoc(result)  # 做10次匹配，取最大值
            scores.append(score)
        groupOuput.append(str(np.argmax(scores)))
    cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 1)
    cv2.putText(image, "".join(groupOuput), (gx, gy - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
    output.extend(groupOuput)
print('银行卡号为:', " ".join("".join(output[i:i + 4]) for i in range(0, len(output), 4)))
cv_show('image_result', image)
